package com.pansoft.openplanet.util;

import android.content.Context;

import com.efounder.chat.http.JFCommonRequestManager;
import com.efounder.common.BaseRequestManager;
import com.efounder.constant.EnvironmentVariable;
import com.efounder.frame.utils.Constants;
import com.efounder.util.EnvSupportManager;
import com.efounder.utils.ResStringUtil;
import com.pansoft.hd.HDWalletUtil;
import com.pansoft.hd.model.HdUTXO;
import com.pansoft.library.utils.OkHttpUtils;
import com.pansoft.openplanet.R;
import com.pansoft.openplanet.bean.Account;
import com.pansoft.openplanet.db.AccountDao;
import com.pansoft.openplanet.widget.TCLoadingDataView;
import com.utilcode.util.LogUtils;
import com.utilcode.util.StringUtils;
import com.utilcode.util.ToastUtils;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.UTXO;
import org.bitcoinj.core.UTXOProvider;
import org.bitcoinj.core.UTXOProviderException;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.UnreadableWalletException;
import org.bitcoinj.wallet.Wallet;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONObject;
import org.spongycastle.util.encoders.Hex;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import okhttp3.FormBody;
import okhttp3.RequestBody;

import static com.pansoft.hd.HDWalletUtil.getDeterministicSeed;
import static com.pansoft.hd.HDWalletUtil.newReceiveAddress;
import static com.pansoft.openplanet.util.BtcNetParamUtil.URL_BTC_GET_BALANCE;
import static com.pansoft.openplanet.util.BtcNetParamUtil.URL_BTC_GET_UTXO;

public class UTXOTransactionManager {

    private NetworkParameters params;
    private static Coin balance = Coin.valueOf(0L);
    /** 找零地址 */
    private String changeAddress;
    /** 付款地址，用来查找account，成功后，更改子地址index */
    private String payAddress4Account;
    private Context context;

    public UTXOTransactionManager() {
        params = BtcNetParamUtil.getBtcNetParams();
    }

    /**
     * 用UTXO进行交易,completeTx()后，将tx转成hex的String，调用接口广播
     *
     * @param utxoList              utxo
     * @param filePath              钱包路径/或者传助记词过来，如果是助记词，那么按空格分割，如果是不是12个，那么判断为路径
     * @param sendAddr              付款地址
     * @param receiveAddr           对方地址
     * @param value                 转账金额
     * @param feePerKb              手续费
     * @param onTransactionListener listener
     */
    public void startTxWithUTXO(final List<UTXO> utxoList, String filePath, String sendAddr, String receiveAddr, Coin value, Coin feePerKb,
                                OnTransactionListener onTransactionListener) {
        try {
            Wallet wallet;
            String[] array = filePath.split(" ");
            if (array == null || array.length != 12) {
                //不是助记词，//本地取出wallet
                wallet = Wallet.loadFromFile(new File(filePath));
            } else {
                //是助记词
                List<String> list = new ArrayList<>();
                for (int i = 0; i < array.length; i++) {
                    list.add(array[i]);
                }
                long creationTimeSeconds = System.currentTimeMillis() / 1000;
                //种子
                DeterministicSeed ds = new DeterministicSeed(list, null, "", creationTimeSeconds);
                wallet = Wallet.fromSeed(BtcNetParamUtil.getBtcNetParams(), ds);
            }

            //设置UTXOProvider
            wallet.setUTXOProvider(new UTXOProvider() {
                @Override
                public List<UTXO> getOpenTransactionOutputs(List<ECKey> keys) throws UTXOProviderException {
                    return utxoList;
                }

                @Override
                public int getChainHeadHeight() throws UTXOProviderException {
                    return Integer.MAX_VALUE;
                }

                @Override
                public NetworkParameters getParams() {
                    return params;
                }
            });

            Transaction tx = new Transaction(params);
            //收款方的output，转账金额和收款地址
            tx.addOutput(value, ScriptBuilder.createOutputScript(LegacyAddress.fromBase58(params, receiveAddr)));
            SendRequest sendRequest = SendRequest.forTx(tx);
            //找零地址设为付款地址
            sendRequest.changeAddress = LegacyAddress.fromBase58(params, sendAddr);
            //设置手续费
            sendRequest.feePerKb = feePerKb;
            //签名等操作，完成tx
            wallet.completeTx(sendRequest);
            //对外广播的hex
            String hexString = new String(Hex.encode(tx.unsafeBitcoinSerialize()));
            LogUtils.d(hexString);
            System.out.println("coins sent. transaction hash: " + tx.getHashAsString());
            LogUtils.d(tx.toString());
            //广播交易
            publishTx(hexString, tx, onTransactionListener);
        } catch (InsufficientMoneyException e) {
            System.out.println("Not enough coins in your wallet. Missing " + e.missing.getValue() + " satoshis are missing (including fees)");
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_asset_btc_transaction_balance_insufficient));
            }
        } catch (Wallet.DustySendRequested dustySendRequested) {
            dustySendRequested.printStackTrace();
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_asset_btc_transaction_balance_enter_low));
            }
        } catch (UnreadableWalletException e) {
            e.printStackTrace();
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
            }
        }

    }

    /**
     * HDbtc钱包的utxo转账
     **/

    public void startHdTxWithUtxo(final List<HdUTXO> utxoList, String mnemonic, String changeAddress, String receiveAddr, Coin value, Coin feePerb,
                                  OnTransactionListener onTransactionListener) {
        try {
            //master key
            DeterministicSeed ds = getDeterministicSeed(HDWalletUtil.mnemonicString2List(mnemonic), "");
            //deterministicKeyChain
            DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(ds).build();

            Transaction tx = new Transaction(params);
            Coin totalValue = Coin.valueOf(0);
            List<ECKey> ecKeys = new ArrayList<>();
            for (int i = 0; i < utxoList.size(); i++) {
                HdUTXO utxo = utxoList.get(i);
                tx.addInput(utxo.getHash(), utxo.getIndex(), utxo.getScript());
                totalValue = totalValue.add(utxo.getValue());
                //派生私钥
                String childPrvKey = HDWalletUtil.getChildPrivateKey(keyChain, utxo.getPathx(), params);
                ECKey ecKey = DumpedPrivateKey.fromBase58(params, childPrvKey).getKey();
                ecKeys.add(ecKey);
            }
            //收款方的output，转账金额和收款地址
            tx.addOutput(value, ScriptBuilder.createOutputScript(LegacyAddress.fromBase58(params, receiveAddr)));
            //计算手续费，为了方便，输出为2，不考虑输出为1的情况
            int txSize = BtcNetParamUtil.computeBtcTxSize(tx.getInputs().size(), 2);
            long fee = (long) TCMathUtil.mul(txSize, feePerb.getValue());
            LogUtils.d("所需手续费", fee);
            tx.addOutput(totalValue.minus(value).minus(Coin.valueOf(fee)),
                    ScriptBuilder.createOutputScript(Address.fromString(params, changeAddress)));
//        //待签名的交易签名hash
            for (int i = 0; i < utxoList.size(); i++) {
                Script redeemScript = ScriptBuilder.createP2PKHOutputScript(ecKeys.get(i));
                Sha256Hash signHash = tx.hashForSignature(i, redeemScript, Transaction.SigHash.ALL, false);
                System.out.println("待发送的hash:" + signHash.toString());
                ECKey.ECDSASignature partySignature = ecKeys.get(i).sign(signHash);
                TransactionSignature partyTransactionSignature = new TransactionSignature(partySignature, Transaction.SigHash.ALL, false);
                Script inputScript = ScriptBuilder.createInputScript(partyTransactionSignature, ecKeys.get(i));
                tx.getInput(i).setScriptSig(inputScript);
            }
            //对外广播的hex
            String hexString = new String(Hex.encode(tx.unsafeBitcoinSerialize()));
            System.out.println(hexString);
            //广播交易
            publishTx(hexString, tx, onTransactionListener);
        } catch (Exception e) {
            e.printStackTrace();
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
            }
        }
    }

    private void publishTx(String hexString, final Transaction tx, final OnTransactionListener onTransactionListener) {
        RequestBody rb = new FormBody.Builder()
                .add("type", BtcNetParamUtil.NET_TYPE)
                .add("rawhex", hexString)
                .add("toUserId", "")
                .add("fromUserId", EnvironmentVariable.getProperty(Constants.CHAT_USER_ID))
                .add("txhash", tx.getHashAsString())
                .build();
        OkHttpUtils.netRequest("post",
//                    TCRequestUtil.BASE_URL + "/btc/publish",
//                "https://tchain.api.btc.com/v3/tools/tx-publish",
                BtcNetParamUtil.URL_BTC_PUBLISH,
                rb, null, new OkHttpUtils.DownloadListener() {
                    @Override
                    public void onSuccess(byte[] bytes) {
                        TCLoadingDataView.dismiss();
                        try {
                            JSONObject jsonObject = new JSONObject(new String(bytes));
                            if (jsonObject.optString("result").equals("success")) {
                                if (onTransactionListener != null) {
                                    onTransactionListener.onTxSuccess(tx);
                                    // FIXME: 19-11-18 
                                    //交易成功之后，找零地址已用过，需要把子地址index加一
                                    AccountDao accountDao = new AccountDao(context);
                                    Account account = accountDao.getAccount(payAddress4Account);
                                    account.setHdBtcMaxChildIndex((Integer.valueOf(account.getHdBtcMaxChildIndex()) + 1) + "");
                                    accountDao.insertOrReplace(account);
                                }
                            } else {
                                if (onTransactionListener != null) {
                                    String message = (jsonObject.optString("message"));
                                    if (StringUtils.isEmpty(message)) {
                                        onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
                                    } else {
                                        onTransactionListener.onTxFail(message);
                                    }

                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            if (onTransactionListener != null) {
                                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
                            }
                        }

                    }

                    @Override
                    public void onFailure(String s) {
                        TCLoadingDataView.dismiss();
                        if (onTransactionListener != null) {
                            onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
                        }
                    }
                });
    }

    /**
     * 获取余额
     */
    public static void getBalance(String tag, Account account) {
        boolean isHdWallet = EnvSupportManager.isSupportHDWallet() &&
                !com.pansoft.openplanet.blockchain.util.StringUtils.isEmpty(account.getHdBtcCurrentChildAddress());
        HashMap<String, String> map = new HashMap<>();
        if (isHdWallet) {
            map.put("xpub", account.getHdBtcXpub());
        } else {
            map.put("BtcAddress", account.getAddress());
        }
        map.put("type", BtcNetParamUtil.NET_TYPE);
        JFCommonRequestManager.getInstance().requestGetByAsyn(tag, URL_BTC_GET_BALANCE, map,
                new BaseRequestManager.ReqCodeCallBack<String>() {
                    @Override
                    public void onReqFailed(int errorCode, String errorMsg) {
                        TCLoadingDataView.dismiss();
                        Coin balance = Coin.valueOf(0);
                        EventBus.getDefault().post(balance);
                    }

                    @Override
                    public void onReqSuccess(String result) {
                        TCLoadingDataView.dismiss();
                        try {
                            JSONObject jsonObject = new JSONObject(result);
                            if (jsonObject.optString("result").equals("success")) {
                                JSONArray jsonArray = jsonObject.optJSONArray("data");
                                JSONObject dataObject = jsonArray.optJSONObject(0);
                                Coin balance = Coin.valueOf(Long.valueOf(dataObject
                                        .optString("finalBalance")));
                                EventBus.getDefault().post(balance);
                            } else {
                                Coin balance = Coin.valueOf(0);
                                EventBus.getDefault().post(balance);
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                            Coin balance = Coin.valueOf(0);
                            EventBus.getDefault().post(balance);
//                            ToastUtils.showShort("请求出错，请重试");
                        }
                    }

                    @Override
                    public void onReqFailed(String errorMsg) {

                    }
                });
    }

    /**
     * 调用接口获取UTXO信息
     */
    public void getUTXO(Context context, String tag, final String payAddress, final String filePath, final String receiveAddress, final Coin value,
                        final Coin gas, final boolean isHdWallet, final OnTransactionListener onTransactionListener) {
        HashMap<String, String> map = new HashMap<>();
        payAddress4Account = payAddress;
        this.context = context;
        //hd钱包使用xpub请求
        if (isHdWallet) {
            Account account = new AccountDao(context).getAccount(payAddress);
            String xpub = account.getHdBtcXpub();
            // FIXME: 19-11-18 找零地址，每次都是新生成一个地址
            changeAddress = newReceiveAddress(BtcNetParamUtil.getBtcBipPath(), xpub,
                    Integer.valueOf(account.getHdBtcMaxChildIndex()) + 1, params).getAddress();
            map.put("xpub", xpub);
        } else {
            map.put("BtcAddress", payAddress);
        }
        map.put("type", BtcNetParamUtil.NET_TYPE);
        JFCommonRequestManager.getInstance().requestGetByAsyn(tag, URL_BTC_GET_UTXO, map, new BaseRequestManager.ReqCodeCallBack<String>() {
            @Override
            public void onReqFailed(int errorCode, String errorMsg) {

            }

            @Override
            public void onReqSuccess(String result) {
                try {
                    JSONObject jsonObject1 = new JSONObject(result);
                    if (jsonObject1.optString("result").equals("success")) {
                        List<UTXO> utxoList = new ArrayList<>();
                        List<HdUTXO> hdUTXOList = new ArrayList<>();
                        JSONArray jsonArray = jsonObject1.optJSONArray("data");
                        if (jsonArray.length() > 0) {
                            for (int i = 0; i < jsonArray.length(); i++) {
                                JSONObject jsonObject = jsonArray.optJSONObject(i);
                                if (isHdWallet) {
                                    HdUTXO utxo = new HdUTXO(Sha256Hash.wrap(jsonObject.optString("transactionHash")),
                                            Long.valueOf(jsonObject.optString("n")),
                                            Coin.valueOf(Long.valueOf(jsonObject.optString("value"))),
                                            0, false,
                                            new Script(org.bouncycastle.util.encoders.Hex.decode(jsonObject.optString("script"))),
                                            jsonObject.optString("path", "/0/0"));
                                    hdUTXOList.add(utxo);
                                } else {
                                    UTXO utxo = new UTXO(Sha256Hash.wrap(jsonObject.optString("transactionHash")),
                                            Long.valueOf(jsonObject.optString("n")),
                                            Coin.valueOf(Long.valueOf(jsonObject.optString("value"))),
                                            0, false,
                                            new Script(org.bouncycastle.util.encoders.Hex.decode(jsonObject.optString("script"))));
                                    utxoList.add(utxo);
                                }
                            }
                            //开始转账
                            if (isHdWallet) {
                                startHdTxWithUtxo(hdUTXOList, filePath, changeAddress, receiveAddress, value,
                                        gas, onTransactionListener);
                            } else {
                                startTxWithUTXO(utxoList, filePath, payAddress, receiveAddress, value,
                                        gas, onTransactionListener);
                            }
                        } else {
                            if (onTransactionListener != null) {
                                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_asset_btc_transaction_balance_insufficient));
                            }
                            ToastUtils.showShort(ResStringUtil.getString(R.string.open_planet_asset_btc_transaction_balance_insufficient));
                        }
                    } else {
                        if (onTransactionListener != null) {
                            onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    if (onTransactionListener != null) {
                        onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
                    }
                }
            }

            @Override
            public void onReqFailed(String errorMsg) {
                if (onTransactionListener != null) {
                    onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_transfer_fail));
                }
            }
        });
    }

    public interface OnTransactionListener {

        void onTxSuccess(Transaction tx);

        void onTxFail(String e);
    }
}
